package cn.edu.buaa.treehole.service;

import cn.edu.buaa.treehole.common.Pair;
import cn.edu.buaa.treehole.config.Setting;
import cn.edu.buaa.treehole.common.cache.CachedFactory;
import cn.edu.buaa.treehole.common.cache.CachedMap;
import cn.edu.buaa.treehole.dao.ReplyDao;
import cn.edu.buaa.treehole.dao.SignatureDao;
import cn.edu.buaa.treehole.dao.exception.DaoException;
import cn.edu.buaa.treehole.dao.exception.PidNotExistException;
import cn.edu.buaa.treehole.dao.exception.SignatureNotExistException;
import cn.edu.buaa.treehole.pojo.ReplyInfo;
import cn.edu.buaa.treehole.pojo.fixedinfo.NamedReplyInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ReplyInfoService {
    private final int postCacheSize = 32;
    private final int replyCacheSize = 64;

    private class CacheFactory implements CachedFactory<Long, CachedMap<Long, NamedReplyInfo>> {
        private class ReplyCacheFactory implements CachedFactory<Long, NamedReplyInfo> {
            private final long pid;

            public ReplyCacheFactory(long pid) {
                this.pid = pid;
            }

            @Override
            public NamedReplyInfo get(Long rid) throws DaoException {
                ReplyInfo replyInfo = replyDao.getReply(pid, rid);
                String name = signatureDao.getName(pid, replyInfo.getSignature());
                return new NamedReplyInfo(
                        replyInfo.getPid(),
                        replyInfo.getRid(),
                        replyInfo.getContent(),
                        name,
                        replyInfo.getDate()
                );
            }
        }

        @Override
        public CachedMap<Long, NamedReplyInfo> get(Long pid) {
            return new CachedMap<>(
                    new ReplyCacheFactory(pid),
                    (Long k, NamedReplyInfo v) -> { },
                    replyCacheSize
            );
        }
    }

    private final CachedMap<Long, CachedMap<Long, NamedReplyInfo>> cache
            = new CachedMap<>(
                    new CacheFactory(),
                    (Long k, CachedMap<Long, NamedReplyInfo> v)->{ },
                    postCacheSize
            );
    private final Logger logger = LoggerFactory.getLogger(ReplyInfoService.class);
    private final ReplyDao replyDao;
    private final SignatureDao signatureDao;
    private NameService nameService;
    private Setting setting;

    @Autowired
    public ReplyInfoService(ReplyDao replyDao, SignatureDao signatureDao) {
        this.replyDao = replyDao;
        this.signatureDao = signatureDao;
    }

    @Autowired
    public void setNameService(NameService nameService) {
        this.nameService = nameService;
    }

    public Setting getSetting() {
        return setting;
    }

    @Autowired
    public void setSetting(Setting setting) {
        this.setting = setting;
    }

    @Override
    public String toString() {
        return "ReplyInfoService{" +
                "postCacheSize=" + postCacheSize +
                ", replyCacheSize=" + replyCacheSize +
                ", cache=" + cache +
                ", replyDao=" + replyDao +
                ", signatureDao=" + signatureDao +
                '}';
    }

    public NamedReplyInfo getReplyInfo(long pid, long rid) throws DaoException {
        if (setting.blockInfoCache()) {
            ReplyInfo replyInfo = replyDao.getReply(pid, rid);
            String name = signatureDao.getName(pid, replyInfo.getSignature());
            return new NamedReplyInfo(
                    replyInfo.getPid(),
                    replyInfo.getRid(),
                    replyInfo.getContent(),
                    name,
                    replyInfo.getDate()
            );
        }
        try {
            return cache.get(pid).get(rid);
        }
        catch (DaoException e) {
            throw e;
        }
        catch (Exception e) {
            String msg = toString() + ".getReplyInfo(" + pid + ", " + rid + ")";
            logger.error(msg, e);
            throw new RuntimeException(msg, e);
        }
    }


    /*
    * @param info: rid不必初始化, 但其余属性都一定完成了初始化.
    * @return 返回分配给该回帖的昵称, 分配给改回帖的rid.
    * */
    public Pair<String, Long> addReply(ReplyInfo info) throws PidNotExistException, DaoException {
        synchronized (replyDao) {
            long rid = replyDao.getLatestReplyRid(info.getPid());
            info.setRid(++rid);
            String name;
            try {
                name = signatureDao.getName(info.getPid(), info.getSignature());
            }
            catch (SignatureNotExistException e) {
                int count = signatureDao.getCount(info.getPid());
                long pid = info.getPid();
                while(true) {
                    name = nameService.getName(count);
                    if (!signatureDao.containName(pid, name)) {
                        signatureDao.addName(pid, info.getSignature(), name);
                        break;
                    }
                }
            }
            replyDao.addReply(info);
            return Pair.valueOf(name, rid);
        }
    }

    public void deleteReply(long pid, long rid, String signature) throws DaoException {
        ReplyInfo info = replyDao.getReply(pid, rid);
        if (!info.getSignature().equals(signature)) {
            throw new SignatureNotExistException(toString()
                    + ".deleteReply(" + pid + ", " + rid + ", " + signature + ")");
        }
        if (!setting.blockInfoCache() && cache.containsKey(pid))
        {
            try {
                cache.get(pid).remove(rid, false);
            }
            catch (Exception e) {
                String msg = toString() + ".deleteReply(" + pid + ", " + rid + ", " + signature + ")";
                logger.error(msg, e);
                throw new RuntimeException(msg, e);
            }
        }
        replyDao.deleteReply(pid, rid);
    }

    public void deleteAllReplyCacheForPost(long pid) {
        if (setting.blockInfoCache()) {
            return;
        }
        if (!cache.containsKey(pid)) {
            return;
        }
        try {
            cache.remove(pid, false);
        }
        catch (Exception e) {
            String msg = toString() + ".deletePost(" + pid + ")";
            logger.error(msg, e);
            throw new RuntimeException(msg, e);
        }
    }
}
